#region Copyright Notice
// 
// 
//  
//  Copyright (c) 2009-2011 Anindya Chatterjee
//  
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//  
//      http://www.apache.org/licenses/LICENSE-2.0
//  
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
// 
// 
#endregion
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using AbstractClass.Collections.Generic;
using AbstractClass.Extensions;
using AbstractClass.Linq;

namespace AbstractClass.Runtime.Caching
{
    /// <summary>
    /// Represents a type which implements an serializable in-memory cache.
    /// </summary>
    /// <typeparam name="TKey">The type of the key.</typeparam>
    /// <typeparam name="TValue">The type of the value.</typeparam>
    [Serializable]
    public class Cache<TKey, TValue> : IEnumerable<TKey>
    {
        private readonly HashMap<TKey, CacheEntry<TKey, TValue>> _cacheStore;
        private IEvictionPolicy<TKey> _evictionPolicy;
        private int _hit, _miss;

        /// <summary>
        /// Initializes a new instance of the <see cref="Cache&lt;TKey, TValue&gt;"/> class.
        /// </summary>
        /// <remarks>Default size of the cache is 1024 and default eviction policy is
        /// <see cref="LruEvictionPolicy&lt;TKey&gt;"/>.</remarks>
        public Cache()
            : this(1024)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Cache&lt;TKey, TValue&gt;"/> class.
        /// </summary>
        /// <param name="capacity">The capacity of the new cache.</param>
        /// <remarks>Default eviction policy is <see cref="LruEvictionPolicy&lt;TKey&gt;"/>.</remarks>
        public Cache(int capacity)
            : this(capacity, new LruEvictionPolicy<TKey>())
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Cache&lt;TKey, TValue&gt;"/> class.
        /// </summary>
        /// <param name="capacity">The capacity of the new cache.</param>
        /// <param name="evictionPolicy">The eviction policy of the cache.</param>
        public Cache(int capacity, IEvictionPolicy<TKey> evictionPolicy)
        {
            _cacheStore = new HashMap<TKey, CacheEntry<TKey, TValue>>(capacity);
            Capacity = capacity;
            _evictionPolicy = evictionPolicy;
        }

        /// <summary>
        /// Gets or sets the eviction policy of the <see cref="Cache&lt;TKey, TValue&gt;"/>.
        /// </summary>
        /// <value>The eviction policy of the cache.</value>
        public IEvictionPolicy<TKey> EvictionPolicy
        {
            get { return _evictionPolicy; }
            set
            {
                _evictionPolicy = value;
                _evictionPolicy.Renew();
                _cacheStore.Keys.Process(_evictionPolicy.RegisterKey);
            }
        }

        /// <summary>
        /// Gets or sets the capacity of the <see cref="Cache&lt;TKey, TValue&gt;"/>.
        /// </summary>
        /// <value>The capacity of the cache.</value>
        public int Capacity { get; protected set; }

        /// <summary>
        /// Gets the number of elements in the <see cref="Cache&lt;TKey, TValue&gt;"/>.
        /// </summary>
        /// <value>The number of elements in the cache.</value>
        public int Count
        {
            get { return _cacheStore.Count; }
        }

        /// <summary>
        /// Gets the read-only list of keys of the <see cref="Cache&lt;TKey, TValue&gt;"/>.
        /// </summary>
        /// <value>The read-only collection of keys.</value>
        public ReadOnlyCollection<TKey> Indices
        {
            get { return new ReadOnlyCollection<TKey>(_cacheStore.Keys.ToList()); }
        }

        /// <summary>
        /// Gets the read-only list of entries stored in the <see cref="Cache&lt;TKey, TValue&gt;"/>.
        /// </summary>
        /// <value>The read-only list of entries in the cache.</value>
        public ReadOnlyCollection<CacheEntry<TKey, TValue>> Entries
        {
            get
            {
                return new ReadOnlyCollection<CacheEntry<TKey, TValue>>(
                    _cacheStore.Values.ToList());
            }
        }

        /// <summary>
        /// Gets the hit ratio of the cache.
        /// </summary>
        /// <value>The hit ratio of the cache.</value>
        public decimal HitRatio
        {
            get { return (decimal) _hit / (_hit + _miss); }
        }

        #region IEnumerable<TKey> Members

        /// <summary>
        /// Returns an enumerator that iterates through the keys stored in the cache.
        /// </summary>
        /// <returns>A sequence of keys stored in the cache.</returns>
        public IEnumerator<TKey> GetEnumerator()
        {
            return _cacheStore.Select(item => item.Key).GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        #endregion

        /// <summary>
        /// Occurs when there is a hit.
        /// </summary>
        /// <remarks>This event is not serializable.</remarks>
        [field: NonSerialized]
        public virtual event CacheOperationHandler<TKey, TValue> Hit;

        /// <summary>
        /// Occurs when there is a miss.
        /// </summary>
        /// <remarks>This event is not serializable.</remarks>
        [field: NonSerialized]
        public virtual event CacheOperationHandler<TKey, TValue> Miss;

        /// <summary>
        /// Occurs when an entry expired from <see cref="Cache&lt;TKey, TValue&gt;"/>.
        /// </summary>
        /// <remarks>This event is not serializable.</remarks>
        [field: NonSerialized]
        public virtual event CacheOperationHandler<TKey, TValue> EntryExpired;

        /// <summary>
        /// Gets the victim entry from the <see cref="Cache&lt;TKey, TValue&gt;"/>.
        /// </summary>
        /// <returns>Key associated with the entry.</returns>
        /// <exception cref="NoEvictionPolicyException">No eviction policy is defined for the cache.</exception>
        public virtual TKey GetVictimEntry()
        {
            if (_evictionPolicy != null)
                return _evictionPolicy.GetVictimEntry();

            throw new NoEvictionPolicyException();
        }

        /// <summary>
        /// Adds an entry into the <see cref="Cache&lt;TKey, TValue&gt;"/>.
        /// </summary>
        /// <param name="key">The key of the entry to add.</param>
        /// <param name="entry">The entry to add.</param>
        public virtual void AddEntry(TKey key, TValue entry)
        {
            var cacheEntry = new CacheEntry<TKey, TValue>(key, entry);
            AddEntry(cacheEntry);
        }

        /// <summary>
        /// Adds an <see cref="CacheEntry&lt;TKey, TValue&gt;"/> into the <see cref="Cache&lt;TKey, TValue&gt;"/>.
        /// </summary>
        /// <param name="entry">The entry to add.</param>
        /// <exception cref="NoEvictionPolicyException">No eviction policy is defined for the cache.</exception>
        protected virtual void AddEntry(CacheEntry<TKey, TValue> entry)
        {
            entry.ThrowIfNull("enrty");

            if (_cacheStore.Count >= Capacity)
            {
                if (_evictionPolicy == null)
                    throw new NoEvictionPolicyException();

                TKey key = _evictionPolicy.GetVictimEntry();
                CacheEntry<TKey, TValue> victim = _cacheStore[key];
                _cacheStore.Remove(key);
                OnEntryExpired(new CacheEventArgs<TKey, TValue>(victim));
            }
            _cacheStore.Add(entry.Key, entry);
            _evictionPolicy.RegisterKey(entry.Key);
        }

        /// <summary>
        /// Removes the entry specified by the key from the <see cref="Cache&lt;TKey, TValue&gt;"/>.
        /// </summary>
        /// <param name="key">The key associated with the entry to remove.</param>
        /// <returns><c>true</c> if the entry is removed sucessfully; otherwise, <c>false</c>.</returns>
        public virtual bool Remove(TKey key)
        {
            // ReSharper disable CompareNonConstrainedGenericWithNull
            if (key == null)
                // ReSharper restore CompareNonConstrainedGenericWithNull
                throw new ArgumentNullException("key");

            _evictionPolicy.Revoke(key);
            return _cacheStore.Remove(key);
        }

        /// <summary>
        /// Determines whether the <see cref="Cache&lt;TKey, TValue&gt;"/> contains the specified key.
        /// </summary>
        /// <param name="key">The key to search in the cache.</param>
        /// <returns>
        /// 	<c>true</c> if the specified key exists in the cache; otherwise, <c>false</c>.
        /// </returns>
        public virtual bool ContainsKey(TKey key)
        {
            // ReSharper disable CompareNonConstrainedGenericWithNull
            if (key == null)
                // ReSharper restore CompareNonConstrainedGenericWithNull
                throw new ArgumentNullException("key");

            return _cacheStore.ContainsKey(key);
        }

        /// <summary>
        /// Determines whether the <see cref="Cache&lt;TKey, TValue&gt;"/> contains the specified value.
        /// </summary>
        /// <param name="value">The value to search in the cache.</param>
        /// <returns>
        /// 	<c>true</c> if the specified value exists in the cache; otherwise, <c>false</c>.
        /// </returns>
        public virtual bool ContainsValue(CacheEntry<TKey, TValue> value)
        {
            return _cacheStore.ContainsValue(value);
        }

        /// <summary>
        /// Fetches an entry from the <see cref="Cache&lt;TKey, TValue&gt;"/> associated with the
        /// specified key. If the key is there in the cache it fires the <see cref="Hit"/> event;
        /// otherwise, <see cref="Miss"/> event.
        /// </summary>
        /// <param name="key">The key associated with the entry to fetch from the cache.</param>
        public virtual void Fetch(TKey key)
        {
            CacheEntry<TKey, TValue> entry = null;

            if (ContainsKey(key))
            {
                _cacheStore.TryGetValue(key, out entry);
                OnHit(new CacheEventArgs<TKey, TValue>(entry));
            }
            else
            {
                OnMiss(new CacheEventArgs<TKey, TValue>(entry)
                           {
                               MissedKey = key
                           });
            }
        }

        /// <summary>
        /// Changes the capacity of the <see cref="Cache&lt;TKey, TValue&gt;"/>.
        /// </summary>
        /// <param name="capacity">The new capacity of the cache.</param>
        public virtual void ReDim(int capacity)
        {
            Capacity = capacity;
        }

        /// <summary>
        /// Raises the <see cref="Hit"/> event.
        /// </summary>
        /// <param name="e">The <see cref="AbstractClass.Runtime.Caching.CacheEventArgs&lt;TKey,TValue&gt;"/> instance containing the event data.</param>
        protected virtual void OnHit(CacheEventArgs<TKey, TValue> e)
        {
            _hit++;
            if (Hit != null)
                Hit.Invoke(this, e);
            if (e.Entry != null)
            {
                _evictionPolicy.RegisterKey(e.Entry.Key);
            }
        }

        /// <summary>
        /// Raises the <see cref="Miss"/> event.
        /// </summary>
        /// <param name="e">The <see cref="AbstractClass.Runtime.Caching.CacheEventArgs&lt;TKey,TValue&gt;"/> instance containing the event data.</param>
        protected virtual void OnMiss(CacheEventArgs<TKey, TValue> e)
        {
            _miss++;
            if (Miss != null)
            {
                Miss.Invoke(this, e);
            }
        }

        /// <summary>
        /// Raises the <see cref="EntryExpired"/> event.
        /// </summary>
        /// <param name="e">The <see cref="AbstractClass.Runtime.Caching.CacheEventArgs&lt;TKey,TValue&gt;"/> instance containing the event data.</param>
        protected virtual void OnEntryExpired(CacheEventArgs<TKey, TValue> e)
        {
            if (EntryExpired != null)
                EntryExpired.Invoke(this, e);

            if (e.Entry != null)
                _evictionPolicy.Revoke(e.Entry.Key);
        }

        /// <summary>
        /// Clears the current <see cref="Cache&lt;TKey, TValue&gt;"/> instance by removing all
        /// entries.
        /// </summary>
        public void Clear()
        {
            _cacheStore.Clear();
        }
    }
}